    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_THREADS_KERNEL_SHARED_CPp_
    #define DLIB_THREADS_KERNEL_SHARED_CPp_

    #include "threads_kernel_shared.h"
    #include "../assert.h"
    #include "../platform.h"
    #include <iostream>

    #ifndef DLIB_THREAD_POOL_TIMEOUT
    // default to 30000 milliseconds
    #define DLIB_THREAD_POOL_TIMEOUT 30000
    #endif

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // threader functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        namespace threads_kernel_shared
        {

            bool thread_pool_has_been_destroyed = false;

    // ----------------------------------------------------------------------------------------

            struct threader_destruct_helper
            {
                // cause the thread pool to begin its destruction process when
                // global objects start to be destroyed
                ~threader_destruct_helper()
                {
                    thread_pool().destruct_if_ready();
                }
            };

    // ----------------------------------------------------------------------------------------

            threader& thread_pool (
            )
            {
                static threader* thread_pool = new threader;
                static threader_destruct_helper a;
                return *thread_pool;
            }

    // ----------------------------------------------------------------------------------------

            bool threader::
            is_dlib_thread (
                thread_id_type id
            )
            {
                auto_mutex M(data_mutex);
                return thread_ids.is_member(id);
            }

    // ----------------------------------------------------------------------------------------

            threader::
            threader (
            ) :
                total_count(0),
                function_pointer(0),
                pool_count(0),
                data_ready(data_mutex),
                data_empty(data_mutex),
                destruct(false),
                destructed(data_mutex),
                do_not_ever_destruct(false)
            {
    #ifdef WIN32
                // Trying to destroy the global thread pool when we are part of a DLL and the
                // DLL is being unloaded can sometimes lead to weird behavior.  For example, in
                // the python interpreter you will get the interpreter to hang.  Or if we are
                // part of a MATLAB mex file and the file is being unloaded there can also be
                // similar weird issues.  So when we are using dlib on windows we just disable
                // the destruction of the global thread pool since it doesn't matter anyway.
                // It's resources will just get freed by the OS.  This is even the recommended
                // thing to do by Microsoft (http://blogs.msdn.com/b/oldnewthing/archive/2012/01/05/10253268.aspx).
                //
                // As an aside, it's worth pointing out that the reason we try and free
                // resources on program shutdown on other operating systems is so we can have
                // clean reports from tools like valgrind which check for memory leaks.  But
                // trying to do this on windows is a lost cause so we give up in this case and
                // follow the Microsoft recommendation.
                do_not_ever_destruct = true;
    #endif // WIN32
            }

    // ----------------------------------------------------------------------------------------

            threader::
            ~threader (
            )
            {
                data_mutex.lock();
                destruct = true;
                data_ready.broadcast();

                // wait for all the threads to end
                while (total_count > 0)
                    destructed.wait();

                thread_pool_has_been_destroyed = true;
                data_mutex.unlock();
            }

    // ----------------------------------------------------------------------------------------

            void threader::
            destruct_if_ready (
            )
            {
                if (do_not_ever_destruct)
                    return;

                data_mutex.lock();

                // if there aren't any active threads, just maybe some sitting around
                // in the pool then just destroy the threader
                if (total_count == pool_count)
                {
                    destruct = true;
                    data_ready.broadcast();
                    data_mutex.unlock();
                    delete this;
                }
                else
                {
                    // There are still some user threads running so there isn't
                    // much we can really do.  Just let the program end without
                    // cleaning up threading resources.
                    data_mutex.unlock();
                }
            }

    // ----------------------------------------------------------------------------------------

            void threader::
            call_end_handlers (
            )
            {
                reg.m.lock();
                const thread_id_type id = get_thread_id();
                thread_id_type id_copy;
                member_function_pointer<> mfp;

                // Remove all the member function pointers for this thread from the tree
                // and call them.
                while (reg.reg[id] != 0)
                {
                    reg.reg.remove(id,id_copy,mfp);
                    reg.m.unlock();
                    mfp();
                    reg.m.lock();
                }
                reg.m.unlock();
            }

        // ------------------------------------------------------------------------------------

            bool threader::
            create_new_thread (
                void (*funct)(void*),
                void* param
            )
            {

                // get a lock on the data mutex
                auto_mutex M(data_mutex);

                // loop to ensure that the new function pointer is in the data
                while (true)
                {
                    // if the data is empty then add new data and quit loop
                    if (function_pointer == 0)
                    {
                        parameter = param;
                        function_pointer = funct;
                        break;
                    }
                    else
                    {
                        // wait for data to become empty
                        data_empty.wait();
                    }
                }

                // get a thread for this new data
                // if a new thread must be created
                if (pool_count == 0)
                {
                    // make thread and add it to the pool
                    if ( threads_kernel_shared_helpers::spawn_thread(thread_starter, this) == false )
                    {
                        function_pointer = 0;
                        parameter = 0;
                        data_empty.signal();
                        return false;
                    }
                    ++total_count;
                }
                // wake up a thread from the pool
                else
                {
                    data_ready.signal();
                }

                return true;
            }

        // ------------------------------------------------------------------------------------

            void thread_starter (
                void* object
            )
            {
                // get a reference to the calling threader object
                threader& self = *static_cast<threader*>(object);

                {
                auto_mutex M(self.data_mutex);

                // add this thread id
                thread_id_type thread_id = get_thread_id();
                self.thread_ids.add(thread_id);

                // indicate that this thread is now in the thread pool
                ++self.pool_count;

                while (self.destruct == false)
                {
                    // if data is ready then process it and launch the thread
                    // if its not ready then go back into the pool
                    while (self.function_pointer != 0)
                    {
                        // indicate that this thread is now out of the thread pool
                        --self.pool_count;

                        // get the data for the function call
                        void (*funct)(void*) = self.function_pointer;
                        void* param = self.parameter;
                        self.function_pointer = 0;

                        // signal that the data is now empty
                        self.data_empty.signal();

                        self.data_mutex.unlock();
                        // Call funct with its intended parameter.  If this function throws then
                        // we intentionally let the exception escape the thread and result in whatever
                        // happens when it gets caught by the OS (generally the program is terminated).
                        funct(param);
                        self.call_end_handlers();

                        self.data_mutex.lock();

                        // indicate that this thread is now back in the thread pool
                        ++self.pool_count;
                    }

                    if (self.destruct == true)
                        break;

                    // if we timed out and there isn't any work to do then
                    // this thread will quit this loop and end.
                    if (self.data_ready.wait_or_timeout(DLIB_THREAD_POOL_TIMEOUT) == false &&
                        self.function_pointer == 0)
                        break;

                }

                // remove this thread id from thread_ids
                thread_id = get_thread_id();
                self.thread_ids.destroy(thread_id);

                // indicate that this thread is now out of the thread pool
                --self.pool_count;
                --self.total_count;

                self.destructed.signal();

                } // end of auto_mutex M(self.data_mutex) block
            }

        // ------------------------------------------------------------------------------------

        }

    // ----------------------------------------------------------------------------------------

        bool is_dlib_thread (
            thread_id_type id
        )
        {
            return threads_kernel_shared::thread_pool().is_dlib_thread(id);
        }

        bool is_dlib_thread (
        )
        {
            return is_dlib_thread(get_thread_id());
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_THREADS_KERNEL_SHARED_CPp_